/*
 * @(#)GameScriptManager.java     14/08/02
 * 
 * Author             Rick Wilson
 * Copyright (c) 2014 Rick Wilson
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of 'Taareekah' nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */



package core;

//~--- non-JDK imports --------------------------------------------------------

import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;

import groovy.util.GroovyScriptEngine;

import org.codehaus.groovy.control.CompilationFailedException;

//~--- JDK imports ------------------------------------------------------------

import java.io.File;
import java.io.IOException;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.management.JMException;

/**
 * Handles loading Groovy script classes and other script related functions.
 * @author rick
 */
public class GameScriptManager {

    /** Field description */
    private GroovyScriptEngine	gse;

    /** Field description */
    private GroovyClassLoader	gcl;

    /** Field description */
    private Map<String, Class>	loaded_component_classes;

    /**
     * Creates a new GroovyClassLoader and GroovyScriptEngine
     * @param script_roots The root directory where the component script files
     * are located.
     * @throws Exception If the class loader or script engine can't be created.
     */
    public GameScriptManager(String[] script_roots) throws Exception {
	try {
	    gcl	= new GroovyClassLoader(ClassLoader.getSystemClassLoader());
	    gse	= new GroovyScriptEngine(script_roots, gcl);
	} catch (IOException i) {
	    Logger.getLogger(GameScriptManager.class.getName()).log(Level.SEVERE,
			     "Couldn't create new GroovyScriptEngine with given roots", i);

	    throw i;
	}

	loaded_component_classes	= new HashMap<String, Class>();
    }

    /**
     * Returns the Map of Component Type Names to their Class
     * @return Returns the Map of Component Type Names to their Class
     */
    public Map<String, Class> getLoadedComponentClasses() {
	return loaded_component_classes;
    }

    /**
     * Method description
     *
     *
     * @param dir
     * @param unloaded
     *
     * @return
     *
     * @throws Exception
     */
    private boolean loadComponentClassesFromDirectory(File dir, Set<File> unloaded) throws Exception {
	for (String filename : dir.list()) {
	    Logger.getLogger(GameScriptManager.class.getName()).log(Level.SEVERE, "File {0}", filename);

	    File	file	= new File(dir.getAbsolutePath() + File.separator + filename);

	    if (file.isDirectory()) {
		Logger.getLogger(GameScriptManager.class.getName()).log(Level.SEVERE, "Is directory {0}", filename);
		loadComponentClassesFromDirectory(file, unloaded);
	    } else {
		try {
		    Class	new_class	= gcl.parseClass(file);

		    loaded_component_classes.put(new_class.getCanonicalName(), new_class);
		    Logger.getLogger(GameScriptManager.class.getName()).log(Level.SEVERE, "Loaded new class {0}",
				     new_class.getCanonicalName());
		} catch (CompilationFailedException e) {
		    unloaded.add(file);
		}
	    }
	}

	return true;
    }

    /**
     *
     * @param root
     * @return
     * @throws Exception
     */
    public boolean loadComponentClasses(String root) throws Exception {
	loaded_component_classes	= null;
	loaded_component_classes	= new HashMap<>();

	File		file_root	= new File(root);
	Set<File>	unloaded	= new HashSet<>();

	loadComponentClassesFromDirectory(file_root, unloaded);

	for (File file : unloaded) {
	    Class	new_class	= gcl.parseClass(file);

	    loaded_component_classes.put(new_class.getCanonicalName(), new_class);
	    Logger.getLogger(GameScriptManager.class.getName()).log(Level.SEVERE, "Loaded new class {0}",
			     new_class.getCanonicalName());
	}

	return true;
    }

    /**
     * Returns the GroovyScriptEngine.
     * @return The GroovyScriptEngine
     */
    public GroovyScriptEngine getGroovyEngine() {
	return gse;
    }

    /**
     * Checks if a class with name exists.
     * @param name The name of the Component
     * @return True if it exists, false otherwise.
     */
    public boolean hasScriptComponentClass(String name) {
	return loaded_component_classes.containsKey(name);
    }

    /**
     *
     * @param name
     * @return
     */
    public Class getScriptComponentClass(String name) {
	return loaded_component_classes.get(name);
    }

    /**
     *
     * @param script_filename
     * @param binding
     * @throws Exception
     */
    public void runScriptWithBinding(String script_filename, Binding binding) throws Exception {
	gse.run(script_filename, binding);
    }

    /**
     * Creates a new script binding.
     * @return A new script binding.
     */
    public Binding createBinding() {
	return new Binding();
    }

    /**
     *
     * @param filename
     * @param source
     * @return
     */
    public boolean parseObjectFromSource(String filename, String source) {
	if (loaded_component_classes.containsKey(filename)) {
	    Logger.getLogger(GameScriptManager.class.getName()).log(Level.SEVERE, "Tried to reload class: {0}",
			     filename);

	    return false;
	}

	try {
	    Class	temp	= gcl.parseClass(source, filename);

	    loaded_component_classes.put(filename, temp);
	} catch (CompilationFailedException c) {
	    Logger.getLogger(GameScriptManager.class.getName()).log(Level.SEVERE,
			     "Compilation failed for file: {0} : {1}", new Object[] { filename,
		    c.getMessage() });

	    return false;
	}

	return true;
    }

    /**
     *
     * @param filename
     * @return
     */
    public boolean parseObjectFromFile(String filename) {
	if (loaded_component_classes.containsKey(filename)) {
	    Logger.getLogger(GameScriptManager.class.getName()).log(Level.SEVERE, "Tried to reload class: {0}",
			     filename);

	    return false;
	}

	try {
	    Class	temp	= gcl.parseClass(new File(filename));

	    loaded_component_classes.put(filename, temp);
	} catch (CompilationFailedException c) {
	    Logger.getLogger(GameScriptManager.class.getName()).log(Level.SEVERE,
			     "Compilation failed for file: {0} : {1}", new Object[] { filename,
		    c.getMessage() });

	    return false;
	} catch (IOException i) {
	    Logger.getLogger(GameScriptManager.class.getName()).log(Level.SEVERE,
			     "File non existant or unreadable: {0} : {1}", new Object[] { filename,
		    i.getMessage() });

	    return false;
	}

	return true;
    }

    /**
     *
     * @param name
     * @return
     * @throws Exception
     */
    public Object createObjectInstance(String name) throws Exception {
	if (!loaded_component_classes.containsKey(name)) {
	    Logger.getLogger(GameScriptManager.class.getName()).log(Level.SEVERE, "Class not loaded: {0}.", name);

	    throw new JMException("Class not loaded: " + name);
	}

	Object	ret	= null;

	try {
	    Class	temp	= loaded_component_classes.get(name);

	    ret	= temp.newInstance();
	} catch (IllegalAccessException e) {
	    Logger.getLogger(GameScriptManager.class.getName()).log(Level.SEVERE,
			     "Illegal Access Exception when creating object: {0}", name);

	    throw e;
	} catch (InstantiationException e) {
	    Logger.getLogger(GameScriptManager.class.getName()).log(Level.SEVERE,
			     "Instantiation Exception when creating object: {0}", name);

	    throw e;
	} catch (ExceptionInInitializerError e) {
	    Logger.getLogger(GameScriptManager.class.getName()).log(Level.SEVERE,
			     "Exception In Initializor Error when creating object: {0}", name);

	    throw e;
	} catch (SecurityException e) {
	    Logger.getLogger(GameScriptManager.class.getName()).log(Level.SEVERE,
			     "Security Exception when creating object: {0}", name);

	    throw e;
	}

	return ret;
    }

    /**
     * Returns the groovy class loader.
     * @return the groovy class loader.
     */
    public GroovyClassLoader getGroovyClassLoader() {
	return gcl;
    }
}


//~ Formatted by Jindent --- http://www.jindent.com
